import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.Semaphore;

import nanoxml.XMLElement;
import nanoxml.XMLParseException;

/**
 * @author Daniel Bruzual
 * @author Sabrina Fernandez
 * @author Manuel Gomez
 * @author Marilyn Nowacka
 */
public class Controlador {
	
	HashMap<String, Proceso> procesos;
	LinkedList<Instruccion> instrucciones;
	
	/*hace la conversion de direccion virtual a real y lleva el
	control de los marcos de pagina */
	ManejadorInstruccion manejadorIns;
	
	/*hilo que ejecuta las instrucciones periodicamente*/
	EjecutadorInstrucciones ejecutador;
	
	//Para acceder a la lista de instrucciones, o a la memoria
	Semaphore mutex;
	
	//numero de marcos de pagina
	int tam_ram;
	
	//indica si ya se arranco el hilo que ejecuta instrucciones
	private boolean iniciado;
	
	//indica si el hilo que ejecuta instrucciones esta pausado o no
	private boolean pausado;
	
	public Controlador(HashMap<String, Proceso> proc, LinkedList<Instruccion> inst, int tam){
		procesos = proc;
		instrucciones = inst;
		tam_ram = tam;
		manejadorIns = new ManejadorInstruccion(tam);
		mutex = new Semaphore(1);
		ejecutador = new EjecutadorInstrucciones(instrucciones, manejadorIns, mutex);
		iniciado = false; //el hilo ejecutador aun no ha empezado a correr
		pausado = true; //el hilo esta "pausado"
	}

	
	/**
	 * Hace que las instrucciones se ejecuten, en secuencia, siguiendo
	 * los tiempos que demora cada una. 
	 */
	public void play(){
		if(!iniciado){
			ejecutador.start();
			iniciado = true;
		} else {
			ejecutador.resume();
		}
		ejecutador.executeAndStop = false;
		pausado = false;
	}
	
	/**
	 * Pausa la ejecucion de instrucciones.
	 */
	public void pause(){
		if(iniciado){
			ejecutador.suspend();
		}
		ejecutador.executeAndStop = true;
		pausado = true;
	}
	
	/**
	 * Hace que se ejecute unicamente la siguiente instruccion
	 */
	public void step(){
		if(!pausado){
			ejecutador.resume();
		} else {
			if(iniciado){
				ejecutador.resume();
			} else {
				ejecutador.executeAndStop = true;
				ejecutador.start();
				iniciado = true;
			}
		}
	}
	
	/**
	 * @param nombre_proc nombre del proceso que ejecuta la instruccion
	 * @param tipo si es de lectura (r) o escritura(w)
	 * @param dir direccion de memoria virtual accedida
	 * @param delay tiempo que tarda la instruccion en ejecutarse
	 */
	public void agregarInstruccion(String nombre_proc, char tipo, int dir, long delay){
		Instruccion nueva;
		if(procesos.containsKey(nombre_proc)){
			try {
				nueva = new Instruccion(procesos.get(nombre_proc), tipo, dir, delay);
				mutex.acquire();
				instrucciones.add(nueva);
				mutex.release();
				System.out.println("Instruccion agregada.");
			} catch (InterruptedException e){
				System.out.println("No se pudo agregar la instruccion.");
			}
		} else {
			System.out.println("Nombre de proceso invalido.");
		}
	}

	public void imprimirMemoria(){
		try {
			mutex.acquire();
			manejadorIns.imprimir();
			mutex.release();
		} catch (InterruptedException e){
		}
	}

	public void imprimirProcesos(){
		Iterator<Proceso> it = procesos.values().iterator();
		while(it.hasNext()){
			System.out.println("\t"+it.next());
		}
	}

	public void imprimirInstrucciones(){
		try {
			mutex.acquire();
			Iterator<Instruccion> it = instrucciones.iterator();
			while(it.hasNext()){
				Instruccion ins = it.next();
				System.out.println(ins);
			}
			mutex.release();
		} catch (InterruptedException e){
		}
		
	}

	public void imprimirEstadisticas(){
		try {
			mutex.acquire();
			Integer hits = new Integer(manejadorIns.getHits());
			Integer miss = new Integer(manejadorIns.getMiss());
			mutex.release();
			System.out.printf("Número de hits: %d\n", hits);
			System.out.printf("Número de misses: %d\n", miss);
			System.out.printf("Porcentaje Hit: %f \n", (hits.floatValue()/(hits.floatValue()+miss.floatValue()))*100);
			System.out.printf("Porcentaje Miss: %f \n", (miss.floatValue()/(hits.floatValue()+miss.floatValue()))*100);
		} catch (InterruptedException e){
		}
		
		
	}

	public static void main(String[] args){
		String filename = null;
		Parser parser = new Parser();
		Controlador control = null;
		BufferedReader entrada;

		//Abrir el archivo xml e intentar parsear
		try {
			filename = args[0];
			if(parser.parseArchivo(filename)==-1){
				System.exit(0);
			} else {
				control = new Controlador(parser.getProcesos(),
										  parser.getInstrucciones(),
										  parser.getTam_ram());
			}


		} catch (IndexOutOfBoundsException e){
			System.exit(0);
		}

		//flujo principal de control
		char opcion = 0;
		String menu = "\t[i] imprimir \n\t[v] ver instrucciones \n\t[n] nueva instruccion \n\t[s] step \n\t[r] run \n\t[p] pause\n\t[e] estadisticas\n\t[?] imprimir este menu\n\t[q] salir del sistema";
		System.out.println("Bienvenido al sistema simulador de memoria.");
		System.out.println("Para el manejo del sistema, utilice las siguientes opciones:");
		System.out.println(menu);		
		while(true){

			System.out.print(">");
			entrada = new BufferedReader(new InputStreamReader(System.in));
			try{
				opcion = (char) entrada.read();
			} catch (IOException e){

			}
			if(opcion == 'i'){ //print
				//Llamar a la impresion de la memoria (Manejador de memoria)
				System.out.println("Procesos: \n\t(nombre, color)");
				control.imprimirProcesos();
				System.out.println("Estado de la memoria:");
				control.imprimirMemoria();
				System.out.println("Páginas con * están sucias (dirty)");
			} else if (opcion == 's'){ //step
				//Ejecutar siguiente instruccion
				control.step();
			}else if (opcion == 'r'){ //run
				//Comenzar la ejecucion secuencial
				control.play();
			}else if (opcion == 'p'){ //step
				//Pausar la ejecucion secuencial
				control.pause();
			}else if (opcion == 'q'){
				System.exit(0);
			} else if (opcion == 'n'){ //agregar instruccion
				try{
					BufferedReader auxReader = new BufferedReader(new InputStreamReader(System.in));
					System.out.print("nombre proceso: ");
				
					String name = auxReader.readLine();
					System.out.print("tipo instruccion ('r' o 'w'): ");
					char type = auxReader.readLine().charAt(0);
					System.out.print("direccion de memoria: ");
					int direc = Integer.valueOf(auxReader.readLine()).intValue();
					System.out.print("duracion: ");
					long durac = Long.valueOf(auxReader.readLine()).longValue();
					control.agregarInstruccion(name, type, direc, durac);
				} catch (Exception e){
					System.out.println("No se pudo agregar la instruccion.");
				}
			} else if (opcion == 'v'){
				control.imprimirInstrucciones();
			} else if (opcion == 'e'){
				control.imprimirEstadisticas();
			} else if (opcion == '?' || opcion == 'h'){
				System.out.println(menu);
			} else {
				System.out.println("Opcion no valida. Escriba '?' para ayuda.");
			}
		}
	}
}
